package engine;

import java.awt.Color;
import java.util.ArrayList;

/**
 *
 * @author Johnny
 */
public class Edge {

    private int id;
    private Color color;
    private Node startNode;
    private Node stopNode;
    private ArrayList<Node> middleNodes;
    private CdpData cdp[];

    public Edge(int id) {
        this(id,null,null,null);
    }

    public Edge(int id, Color color, Node startNode, Node stopNode) {
        this.id = id;
        this.color = color;
        this.startNode = startNode;
        this.stopNode = stopNode;
        this.middleNodes = new ArrayList<Node>();
    }

    public Edge(int id, Color color, Node startNode, Node stopNode, ArrayList<Node> middleNodes) {
        this.id = id;
        this.color = color;
        this.startNode = startNode;
        this.stopNode = stopNode;
        this.middleNodes = middleNodes;
    }

    public Color getColor() {
        return color;
    }

    public int getId() {
        return id;
    }

    public ArrayList<Node> getMiddleNodes() {
        return middleNodes;
    }

    public Node getStartNode() {
        return startNode;
    }

    public Node getStopNode() {
        return stopNode;
    }

    public void setColor(Color color) {
        this.color = color;
    }

    public void setStartNode(Node startNode) {
        this.startNode = startNode;
    }

    public void setStopNode(Node stopNode) {
        this.stopNode = stopNode;
    }

    // Package level methods seen only by the Engine
    void modEdge(Color color, Node startNode, Node stopNode) {
        this.color = color;
        this.startNode = startNode;
        this.stopNode = stopNode;
    }

    /**
     * Adds a node to the list of middle nodes.
     * @param node The node to be added.
     */
    void addMiddleNode(Node node) {
        middleNodes.add(node);
    }

    /**
     * Removes a node from the list of middle nodes.
     * @param nodeId the id of the node to be removed.
     */
    void removeMiddleNode(int nodeId) {
        for (int i = 0; i < middleNodes.size(); i++) {
            if (middleNodes.get(i).getId() == nodeId) {
                middleNodes.remove(i);
                break;
            }
        }
    }

    /**
     * Calculates the position of a point from the percentage of the edge covered.
     * @param perc The percentage value the coordinates should be calculated from. A number between 0 and 100.
     * @return The position represented by a Node.
     */
    Node getAbstractPos(float perc) {
        int upper = 0;

        for (int i = 0; i < cdp.length; i++) {
            if (cdp[i].getPerc() > perc) { // We found the limiting node
                upper = i;
                break;
            } else if (cdp[i].getPerc() == perc) { // It's right on a node
                return (Node)cdp[i].getNode().clone();
            }
        }

        // If we haven't exited yet, let's calculate the position.
        Node node1 = cdp[upper - 1].getNode();
        Node node2 = cdp[upper].getNode();
        float deltaX = node2.getX() - node1.getX();
        float deltaY = node2.getY() - node1.getY();

        // Lets calculate the distance between the tho nodes
        float nodeDist = nodeDist(node1, node2);
        // Lets calculate the relative position of the unit between the nodes.
        float relPos = (perc - cdp[upper - 1].getPerc()) / (cdp[upper].getPerc() - cdp[upper - 1].getPerc());
        // Lets calculate the unit's real distance from node1.
        float unitDist = relPos * nodeDist;

        // cos alpha * d + offset
        float newX = (deltaX / nodeDist) * unitDist + node1.getX();
        // sin alpha * d + offset
        float newY = (deltaY / nodeDist) * unitDist + node1.getY();

        return new Node(0, newX, newY, "");
    }

    /**
     * Recalculates the cumulative distance percentage vector.
     */
    void recalcCdp() {
        // Load the cdp with nodes
        cdp = new CdpData[middleNodes.size() + 2];
        cdp[0] = new CdpData();
        cdp[0].setNode(startNode);
        cdp[0].setPerc(0);
        cdp[cdp.length - 1] = new CdpData();
        cdp[cdp.length - 1].setNode(stopNode);
        cdp[cdp.length - 1].setPerc(100);

        int i = 1;
        for (Node node : middleNodes) {
            cdp[i] = new CdpData();
            cdp[i].setNode(node);
            i++;
        }

        float allDist = 0;
        for (i = 1; i < cdp.length; i++) {
            allDist += nodeDist(cdp[i - 1].getNode(), cdp[i].getNode());
        }

        for (i = 1; i < cdp.length - 1; i++) {
            Node node1 = cdp[i - 1].getNode();
            Node node2 = cdp[i].getNode();
            float ratio = nodeDist(node1, node2) / allDist;
            cdp[i].setPerc(cdp[i - 1].getPerc() + ratio * 100);
        }
    }

    /**
     * Calculates the distance of two nodes.
     * @param node1 
     * @param node2
     * @return The distance.
     */
    private static float nodeDist(Node node1, Node node2) {
        float deltaX = Math.abs(node1.getX() - node2.getX());
        float deltaY = Math.abs(node1.getY() - node2.getY());

        return (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY);
    }

    /**
     * Returns the cumulative distance percentage vector.
     * @return the cumulative distance percentage vector.
     */
    CdpData[] getCdp() {
        return cdp;
    }

    /**
     * Returns the reference of the contained node.
     * @param nodeId the id of the node that will be searched.
     * @return the reference to the node or null if there isn't any.
     */
    Node hasNode(int nodeId) {
        if (startNode.getId() == nodeId)
            return startNode;
        if (stopNode.getId() == nodeId)
            return stopNode;

        Node dummy = new Node(nodeId);
        int index;
        if ((index = middleNodes.indexOf(dummy)) != -1)
            return middleNodes.get(index);
        
        return null;
    }

    /**
     * Determines if the edge is suitable for traveling from start node to stop
     * node.
     * @param startNodeId the id of the start node.
     * @param stopNodeId the id of the stop node.
     * @return 1 if it's a direct route, -1 if it's a reverse route and 0 if
     * it's not suitable.
     */
    int suitable(int startNodeId, int stopNodeId) {
        if (startNodeId == startNode.getId() && stopNodeId == stopNode.getId())
            return 1;
        else if (startNodeId == stopNode.getId() && stopNodeId == startNode.getId())
            return -1;
        else
            return 0;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Edge other = (Edge) obj;
        if (this.id != other.id) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 29 * hash + this.id;
        return hash;
    }

    @Override
    public String toString() {
//        StringBuffer sb = new StringBuffer();
//        sb.append(id);
//        sb.append(" (");
//        sb.append(color.getRed());
//        sb.append(',');
//        sb.append(color.getGreen());
//        sb.append(',');
//        sb.append(color.getBlue());
//        sb.append("): ");
//
//        sb.append(startNode.getId());
//        sb.append(',');
//        for (Node node : middleNodes) {
//            sb.append(node.getId());
//            sb.append(',');
//        }
//        sb.append(stopNode.getId());
//
//        return sb.toString();
        return startNode.getName() + "-" + stopNode.getName();
    }
}
